package com.ticket.sass.admin.interceptor;


import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ticket.sass.admin.annotation.AuthIgnore;
import com.ticket.sass.admin.annotation.Loggable;
import com.ticket.sass.admin.dto.output.admin.AdminInfoOutput;
import com.ticket.sass.admin.handler.IdentityInput;
import com.ticket.sass.admin.manager.ThreadLocalIdInputHolderManager;
import com.ticket.sass.admin.service.impl.CinemaServiceImpl;
import com.ticket.sass.admin.service.impl.OperationLogServiceImpl;
import com.ticket.sass.admin.service.impl.TenantServiceImpl;
import com.ticket.sass.admin.util.*;
import com.ticket.sass.common.entity.provider.TenantEntity;
import com.ticket.sass.common.entity.tenant.CinemaEntity;
import com.ticket.sass.common.entity.tenant.OperationLogEntity;
import com.ticket.sass.common.exception.enums.ErrorCode;
import com.ticket.sass.common.exception.exception.BadRequestException;
import com.ticket.sass.common.exception.exception.UnauthorizedException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author ywb
 * @date 2023-05-24 16:17
 */
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class AuthenticationInterceptor implements HandlerInterceptor {
    @Autowired
    private JwtUtil jwtUtil;
    
    @Autowired
    private RedisCacheUtil redisCache;
    
    @Autowired
    private CinemaServiceImpl cinemaService;
    
    @Autowired
    private TenantServiceImpl tenantService;
    
    @Autowired
    private OperationLogServiceImpl operationLogService;

    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        log.info("====================进入请求过滤器链============================");
        log.info(
            "请求方法类型【{}】, 请求IP【{}】，访问路径【{}】",
            request.getMethod(),
            ResponseUtil.getRemoteHost(request),
            request.getRequestURI()
        );
        // 不属于方法放过
        if (!(handler instanceof HandlerMethod method)) {
            return true;
        }
        String url = request.getRequestURL().toString();
 
        // 定义正则表达式，匹配 URL 中的数字部分，包括子域名
        String regex = "https?://(\\d+)\\.\\w+\\.\\w+|^(\\d+)\\.\\w+\\.\\w+";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(url);
        TenantEntity tenant;
        if (matcher.find()) {
            // 获取第一个匹配组（http/https子域名）或第二个匹配组（直接域名）
            String key = matcher.group(1) != null ? matcher.group(1) : matcher.group(2);
            if (key.length() != 8){
                throw new UnauthorizedException(ErrorCode.UNAUTHORIZED, "域名不正确");
            }

            tenant = tenantService.getOneByKey(key);

            if (Objects.isNull(tenant)) {
                throw new UnauthorizedException(ErrorCode.UNAUTHORIZED, "租户不存在");
            }
            log.info(
                "请求URL【{}】, 租户【{}】，租户名【{}】",
                url,
                key,
                tenant.getName()
            );
            IdentityInput identityInput = new IdentityInput(tenant.getId());
            Long cinemaId = CinemaIdUtil.getCinemaIdFromRequest();
            identityInput.setCinemaId(cinemaId);
            identityInput.setTenantKey(key);
            
            ThreadLocalIdInputHolderManager.setValue(identityInput);
//            verifyCinemaExist(tenant.getId(), cinemaId);
        } else {
            throw new UnauthorizedException(ErrorCode.UNAUTHORIZED, "域名不正确");
        }
        
        
        // 日志记录
        if (method.getMethod().isAnnotationPresent(Loggable.class)) {
            Loggable annotation = method.getMethodAnnotation(Loggable.class);
            // 创建 OperationLogEntity 对象并保存到数据库
            assert annotation != null;
            OperationLogEntity operationLog = new OperationLogEntity(annotation.module().toString(), annotation.desc());
            operationLog.setSuccess(Boolean.FALSE);
            operationLogService.save(operationLog);
            
            // 将请求体内容缓存到请求属性中 将实体其余属性 余到接口请求结束后修改
            request.setAttribute("operationLogId", operationLog.getId().toString());
        }
        
        // 放行
        if (method.getMethod().isAnnotationPresent(AuthIgnore.class)) {
            return true;
        }

        // 获取 TOKEN
        String authToken = jwtUtil.getAuthToken(request);

        // 未携带 token 拦截下来
        if (StrUtil.isBlank(authToken)) {
            throw new UnauthorizedException(
                ErrorCode.UNAUTHORIZED,
                ErrorCode.UNAUTHORIZED.getReasonPhrase()
            );
        }
        
        try {
            jwtUtil.verify(authToken).map(decodedJWT -> {
                String aud = decodedJWT.getClaims().get("aud").asString();
                if (aud == null) {
                    throw new UnauthorizedException(ErrorCode.UNAUTHORIZED, ErrorCode.UNAUTHORIZED.getReasonPhrase());
                }
                Long id = Long.valueOf(aud);
                String cacheObject = redisCache.getCacheObject(jwtUtil.validTokenCacheKey(id));
                AdminInfoOutput admin = GsonUtil.getInstance().fromJson(cacheObject, AdminInfoOutput.class);
                if (Objects.isNull(admin)){
                    log.info("token校验通过，但未通过登录接口发行，伪造token 即将被捕获");
                    throw new UnauthorizedException(
                        ErrorCode.UNAUTHORIZED,
                        ErrorCode.UNAUTHORIZED.getReasonPhrase()
                    );
                }
                if (!admin.getEnable()){
                    throw new UnauthorizedException(
                        ErrorCode.UNAUTHORIZED,
                        "账户已禁用"
                    );
                }
                
                if (!Objects.equals(admin.getTenantId(), tenant.getId())){
                    throw new UnauthorizedException(
                        ErrorCode.UNAUTHORIZED,
                        "无该租户权限"
                    );
                }
                
                redisCache.setKeyExpireTime(jwtUtil.validTokenCacheKey(id), 2, TimeUnit.DAYS);
                response.setHeader("Authorization", jwtUtil.createToken(admin.getId(), admin.getUsername()).getToken());
                response.setHeader("Access-Control-Expose-Headers","Authorization");
                
                return true;
            }).orElseThrow(() -> new UnauthorizedException(ErrorCode.UNAUTHORIZED, ErrorCode.UNAUTHORIZED.getReasonPhrase()));
        } catch (JWTDecodeException exception) {
            throw new UnauthorizedException(ErrorCode.UNAUTHORIZED, ErrorCode.UNAUTHORIZED.getReasonPhrase());
        }
        return true;
    }
    
    
    /**
     * 校验影院是否绑定在租户下面
     *
     * @param tenantId 租户id
     * @param cinemaId 影院id
     * @throws BadRequestException 如果影院不存在，则抛出 BadRequestException 异常
     */
    private void verifyCinemaExist(Long tenantId, Long cinemaId) {
        if (!isCinemaExist(tenantId, cinemaId)) {
            throw new BadRequestException(ErrorCode.BAD_REQUEST, "影院不存在");
        }
    }

    @Cacheable(value = "cinemaExistCache", key = "#tenantId + '_' + #cinemaId")
    public boolean isCinemaExist(Long tenantId, Long cinemaId) {
        if (Objects.equals(0L, cinemaId)){
            return true;
        }
        return cinemaService.count(new QueryWrapper<CinemaEntity>().eq("tenant_id", tenantId).eq("id", cinemaId)) > 0L;
    }
}
